package se.spot4j.model;

import java.io.IOException;
import java.io.StringReader;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.entity.BufferedHttpEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;
import org.jdom.Attribute;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.Namespace;
import org.jdom.input.SAXBuilder;
import org.jdom.xpath.XPath;

import se.spot4j.entity.Album;
import se.spot4j.entity.Artist;
import se.spot4j.entity.Track;
import se.spot4j.exceptions.Spot4JConnectionException;
import se.spot4j.exceptions.Spot4JMalformedRequestException;
import se.spot4j.exceptions.Spot4JRateLimitOversteppedException;
import se.spot4j.exceptions.Spot4JServiceUnavailableException;
import se.spot4j.exceptions.UnexpectedSpotifyResponseException;

public class SpotifyHandlerImpl implements SpotifyHandler {
	 private static final String BASEURL = "http://ws.spotify.com";
	  private static final String SEARCH = "/search";
	  private static final String LOOKUP = "/lookup";
	  private static final String URI = "/?uri=";
	  private static final String VERSION = "/1";
	  private static final String ALBUM = "/album?q=";
	  private static final String ALBUMDETAIL = "&extras=albumdetail";
	  private static final String ARTIST = "/artist?q=artist:";
	  private static final String TRACK = "/track?q=";
	  private static final String TRACKDETAIL = "&extras=trackdetail";
	  private static final String NAMESPACE = "http://www.spotify.com/ns/music/1";
	  private  HttpClient httpclient = new DefaultHttpClient();
	  private HttpGet get;
	   
	  /**
	   * @param name as String
	   * @return A list of Artists. If no artists were found it returns an empty list
	   */
	  public List<Artist> findArtistsByName(String name) throws Spot4JConnectionException, UnexpectedSpotifyResponseException, Spot4JRateLimitOversteppedException, Spot4JMalformedRequestException, Spot4JServiceUnavailableException {
			List<Artist> artists = new Vector<Artist>();
			Artist artist;
			Document doc;			
			try {
				name = URLEncoder.encode(name, "UTF-8");
				String query = BASEURL + SEARCH + VERSION + ARTIST +  "%22" + name + "%22";
				System.out.println(query);
				doc = connectAndExecuteQuery(query);
				XPath artistPath = XPath.newInstance("//ns:artist");
			    artistPath.addNamespace("ns", NAMESPACE);
			    List albumElements = artistPath.selectNodes(doc);
			    Iterator i = albumElements.iterator();
			    while (i.hasNext()) {
			    	Element xmlArtist = (Element)i.next();
			    	if (xmlArtist != null) {
			    		artist = createArtistFromElement(xmlArtist);
			    		
			    		artists.add(artist);
			    	}
			   }
			    
			} catch (Spot4JConnectionException e) {
						
			} catch (UnexpectedSpotifyResponseException e) {
						
			} catch (UnsupportedEncodingException e) {
				
			} catch (JDOMException e) {
				
			}

			return artists;
	         
		}


	  /**
	   * Loads an artist from a spotify uri. Also loads a list of albums the artist
	   * has made or is included in. Will not load tracks as this can be loaded separately.
	   * @param A spotify URI
	   * @return An Artist. If no artist was found, an empty Artist is returned
	   */
	public Artist findArtistBySpotifyURI(String uri) throws Spot4JConnectionException, UnexpectedSpotifyResponseException, Spot4JRateLimitOversteppedException, Spot4JRateLimitOversteppedException, Spot4JMalformedRequestException, Spot4JServiceUnavailableException  {
		Artist artist = new Artist();
		Document doc;
		try {
			uri = URLEncoder.encode(uri, "UTF-8");
			String query = BASEURL + LOOKUP + VERSION + URI + uri + ALBUMDETAIL;
			doc = connectAndExecuteQuery(query);
			XPath artistPath = XPath.newInstance("/ns:artist");
		    artistPath.addNamespace("ns", NAMESPACE);
		    List artistElements = artistPath.selectNodes(doc);
		    Iterator i = artistElements.iterator();
		    while (i.hasNext()) {
		    	Element xmlArtist = (Element)i.next();
		    	if (xmlArtist != null) {
		    		artist = createArtistFromElement(xmlArtist);
		    		Element albumsElement = getChildFromElement(xmlArtist, "albums");
		    		List<Element> xmlAlbums = getChildrenFromElement(albumsElement, "album");
		    		Iterator a = xmlAlbums.iterator();
		    		while (a.hasNext()) {
		    			Album album = createAlbumFromElement((Element)a.next());
		    			artist.getAlbums().add(album);
		    		}
		    	}
		    }
		
		} catch (IOException e) {
			throw new Spot4JConnectionException();
		} catch (JDOMException e) {
			throw new UnexpectedSpotifyResponseException();
		}
		return artist;
	}

	/**
	 * Loads a list of Albums matching the name parameter. 
	 * @param name as String
	 * @return A List<Album> If no albums were found it will return an empty list.
	 */
	public List<Album> findAlbumsByName(String name) throws Spot4JConnectionException, UnexpectedSpotifyResponseException, Spot4JRateLimitOversteppedException, Spot4JRateLimitOversteppedException, Spot4JMalformedRequestException, Spot4JServiceUnavailableException {
		List<Album> albums = new Vector<Album>();
		Album album;
		Document doc;
		
		
		try {
			name = URLEncoder.encode(name, "UTF-8");
			String query = BASEURL + SEARCH + VERSION + ALBUM + name;
			doc = connectAndExecuteQuery(query);
			XPath albumPath = XPath.newInstance("//ns:album");
		    albumPath.addNamespace("ns", NAMESPACE);
		    List albumElements = albumPath.selectNodes(doc);
		    Iterator i = albumElements.iterator();
		    while (i.hasNext()) {
		    	Element xmlAlbum= (Element)i.next();
		    	if (xmlAlbum != null) {
		    		album  = createAlbumFromElement(xmlAlbum);
		    		
		    		albums.add(album);
		    	}
		   }
		    
		} catch (Spot4JConnectionException e) {
					
		} catch (UnexpectedSpotifyResponseException e) {
					
		} catch (UnsupportedEncodingException e) {
			
		} catch (JDOMException e) {
			
		}

		return albums;
         
	}
	/**
	 * Loads an Album from a spotify URI. Also loads tracks included in that album.
	 * @param A spotify URI
	 * @return An Album
	 */
	public Album findAlbumBySpotifyURI(String uri) throws Spot4JConnectionException, UnexpectedSpotifyResponseException, Spot4JRateLimitOversteppedException, Spot4JRateLimitOversteppedException, Spot4JMalformedRequestException, Spot4JServiceUnavailableException  {
		Album album = new Album();
		Document doc;
		try {
			uri = URLEncoder.encode(uri, "UTF-8");
			String query = BASEURL + LOOKUP + VERSION + URI + uri + TRACKDETAIL;
			doc = connectAndExecuteQuery(query);
			XPath albumPath = XPath.newInstance("/ns:album");
		    albumPath.addNamespace("ns", NAMESPACE);
		    List albumElements = albumPath.selectNodes(doc);
		    Iterator i = albumElements.iterator();
		    while (i.hasNext()) {
		    	Element xmlAlbum = (Element)i.next();
		    	if (xmlAlbum != null) {
		    		album = createAlbumFromElement(xmlAlbum);
		    		Element tracksElement = getChildFromElement(xmlAlbum, "tracks");
		    		List<Element> xmlTracks = getChildrenFromElement(tracksElement, "track");
		    		Iterator a = xmlTracks.iterator();
		    		while (a.hasNext()) {
		    			Track track = createTrackFromElement((Element)a.next());
		    			album.getTracks().add(track);
		    		}
		    	}
		    }
		
		} catch (IOException e) {
			throw new Spot4JConnectionException();
		} catch (JDOMException e) {
			throw new UnexpectedSpotifyResponseException();
		}
		return album;
	}
	
	/**
	 * Loads a list of Tracks from a desired name.  
	 * @param name The name of the track
	 * @return A List<Track> Returns an empty list if no tracks were found.
	 */
	public List<Track> findTracksByName(String name) throws Spot4JConnectionException, UnexpectedSpotifyResponseException, Spot4JRateLimitOversteppedException, Spot4JRateLimitOversteppedException, Spot4JMalformedRequestException, Spot4JServiceUnavailableException {
		List<Track> tracks = new Vector<Track>();
		Track track;
		Document doc;
		
		
		try {
			name = URLEncoder.encode(name, "UTF-8");
			String query = BASEURL + SEARCH + VERSION + TRACK + name;
			doc = connectAndExecuteQuery(query);
			XPath trackPath = XPath.newInstance("//ns:track");
		    trackPath.addNamespace("ns", NAMESPACE);
		    List trackElements = trackPath.selectNodes(doc);
		    Iterator i = trackElements.iterator();
		    while (i.hasNext()) {
		    	Element xmlTrack = (Element)i.next();
		    	if (xmlTrack != null) {
		    		track  = createTrackFromElement(xmlTrack);
		    		
		    		tracks.add(track);
		    	}
		   }
		    
		} catch (Spot4JConnectionException e) {
					
		} catch (UnexpectedSpotifyResponseException e) {
					
		} catch (UnsupportedEncodingException e) {
			
		} catch (JDOMException e) {
			
		}

		return tracks;
         
	}
	
	/**
	 * Loads a Track from a spotify URI. 
	 * @param uri A spotify URI
	 * @return A Track. If no track was found it will return an empty Track
	 */
	public Track findTrackBySpotifyURI(String uri) throws Spot4JConnectionException, UnexpectedSpotifyResponseException, Spot4JRateLimitOversteppedException, Spot4JRateLimitOversteppedException, Spot4JMalformedRequestException, Spot4JServiceUnavailableException  {
		Track track = new Track();
		Document doc;
		try {
			uri = URLEncoder.encode(uri, "UTF-8");
			String query = BASEURL + LOOKUP + VERSION + URI + uri;
			doc = connectAndExecuteQuery(query);
			XPath trackPath = XPath.newInstance("/ns:track");
		    trackPath.addNamespace("ns", NAMESPACE);
		    List artistElements = trackPath.selectNodes(doc);
		    Iterator i = artistElements.iterator();
		    while (i.hasNext()) {
		    	Element xmlTrack = (Element)i.next();
		    	if (xmlTrack != null) {
		    		track = createTrackFromElement(xmlTrack);
		    		
		    	}
		    }
		
		} catch (IOException e) {
			throw new Spot4JConnectionException();
		} catch (JDOMException e) {
			throw new UnexpectedSpotifyResponseException();
		}
		return track;
	}
	
	/**
	 * Converts the album element and it's children to a java Album
	 * @param e The album element
	 * @return An Album
	 */
	private Album createAlbumFromElement(Element e) {
		Album a = new Album();
		a.setName(getValueFromElementChild(e, "name", e.getNamespace()));
		a.setSpotifyURI(getAttributeValueFromElement(e, "href"));
		a.setReleaseYear(getValueFromElementChild(e, "released", e.getNamespace()));
		Element xmlArtist = e.getChild("artist", e.getNamespace()); 
		if (xmlArtist != null) {

			a.setArtistName(getValueFromElementChild(xmlArtist, "name", xmlArtist.getNamespace()));
			a.setArtistURI(getAttributeValueFromElement(xmlArtist, "href"));
		}
		
		Element tracksElement = getChildFromElement(e, "tracks");
		if (tracksElement != null) {
			List xmlTracks = getChildrenFromElement(e, "track");
			System.out.println(xmlTracks.size());
			Iterator i = xmlTracks.iterator();
			while (i.hasNext()) {
				a.getTracks().add(createTrackFromElement((Element)i.next()));
			}
		}
		
		return a;
	}
	
	/**
	 * Gets specified children from a parent element. If no children was found it returns an empty list
	 * @param e The element that has children.
	 * @param childName The name of the children to fetch
	 * @return A list of children as Elements.
	 */
	private List<Element> getChildrenFromElement(Element e, String childName) {
		List<Element> children = e.getChildren(childName, e.getNamespace());
		if (children != null) {
			return children;
		} else {
			return new Vector<Element>();
		}
	}
	
	/**
	 * Gets a child element from a parent element. If no element was found an element with the childname is returned
	 * @param e The parent element
	 * @param childName the name of the child element
	 * @return The child element
	 */
	private Element getChildFromElement(Element e, String childName) {
		Element child = e.getChild(childName, e.getNamespace());
		if (child != null) {
			return child;
		} else {
			return new Element(childName);
		}
	}
	
	/**
	 * Converts an artist element to a java Artist. 
	 * @param e The artist element
	 * @return the converted Artist
	 */
	private Artist createArtistFromElement(Element e) {
		Artist a = new Artist();
		a.setName(getValueFromElementChild(e, "name", e.getNamespace()));
		a.setSpotifyURI(getAttributeValueFromElement(e, "href"));
		a.setPopularity(getValueFromElementChild(e, "popularity", e.getNamespace()));
		return a;
	}
	
	/**
	 * Converts a track element to a java Track
	 * @param e the track element
	 * @return the converted Track
	 */
	private Track createTrackFromElement(Element e) {
		Track t = new Track();
		t.setName(getValueFromElementChild(e, "name", e.getNamespace()));
		t.setSpotifyUri(getAttributeValueFromElement(e, "href"));
		t.setPopularity(getValueFromElementChild(e, "popularity", e.getNamespace()));
		t.setDiscNumber(getValueFromElementChild(e, "length", e.getNamespace()));
		Element artistElement = getChildFromElement(e, "artist");
		t.setArtistName(getValueFromElementChild(artistElement, "name", artistElement.getNamespace()));
		t.setArtistURI(getAttributeValueFromElement(artistElement, "href"));
		Element albumElement = getChildFromElement(e, "album");
		t.setAlbumName(getValueFromElementChild(albumElement, "name", albumElement.getNamespace()));
		t.setAlbumURI(getAttributeValueFromElement(albumElement, "href"));
		return t;
	}
	
	/**
	 * Gets value from a child of a parent element. Returns an empty String if no value was returned
	 * @param e The parent element
	 * @param elementName the element to get value from
	 * @param ns namespace
	 * @return The element value
	 */
	private String getValueFromElementChild(Element e, String elementName, Namespace ns) {
		Element a = e.getChild(elementName, ns);
		if (a != null) {
			return a.getValue();
		} else {
			return "";
		}
	}
	
	/**
	 * Gets attribute value from an element
	 * @param e element to get value from
	 * @param attributeName the name of the attribute
	 * @return The attribute value
	 */
	private String getAttributeValueFromElement(Element e, String attributeName) {
		Attribute a = e.getAttribute(attributeName);
		if (a != null) {
			return a.getValue();
		} else {
			return "";
		}
	}
	

	/**
	 * Connects to spotifys webservice and executes the query. If query did not get any results it will return an empty Document
	 * @param query the query as string
	 * @return A jdom Document.
	 * @throws Spot4JConnectionException
	 * @throws UnexpectedSpotifyResponseException
	 */
	private Document connectAndExecuteQuery(String query) throws Spot4JConnectionException, UnexpectedSpotifyResponseException, Spot4JMalformedRequestException, Spot4JServiceUnavailableException, Spot4JRateLimitOversteppedException {
		Document doc = new Document();
		try {
			get = new HttpGet(query);
			HttpResponse response = httpclient.execute(get);
			HttpEntity entity = response.getEntity();
			ResponseHandler responseHandler = new ResponseHandler();
			responseHandler.handleResponse(response);
			if (entity != null) {
				entity = new BufferedHttpEntity(entity);
				//entity.writeTo(System.out);
				SAXBuilder saxbuilder = new SAXBuilder();
				 doc = saxbuilder.build(new StringReader(EntityUtils.toString(entity)));
			} 
			return doc;
		} catch (IOException e) {
			throw new Spot4JConnectionException();
		} catch (JDOMException e) {
			throw new UnexpectedSpotifyResponseException();
		}
	}


//	private synchronized Document connectAndExecuteQuery(String query) throws Spot4JConnectionException, UnexpectedSpotifyResponseException, Spot4JRateLimitOverstepped  {
//		Document doc = new Document();
//		HttpURLConnection conn;
//		try {
//			URL url = new URL(query);
//			 conn = (HttpURLConnection)url.openConnection();
//			conn.connect();
//			//System.out.println(conn.getResponseCode());
//			if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
//				SAXBuilder saxbuilder = new SAXBuilder();
//				doc = saxbuilder.build(conn.getInputStream());
//				
//			} else if (conn.getResponseCode() == HttpURLConnection.HTTP_FORBIDDEN) {
//				throw new Spot4JRateLimitOverstepped();
//			}
//			conn.disconnect();
//		} catch (MalformedURLException e) {
//			throw new Spot4JConnectionException();
//		} catch (IOException e) {
//			throw new Spot4JConnectionException();
//		} catch (JDOMException e) {
//			throw new UnexpectedSpotifyResponseException();
//		} finally {
//			
//		}
//		return doc;
//	}
	
}
